using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MessageServer.channels;
using MessageServer.utilities;

namespace MessageServer.Handlers
{
    //defines the different states the reader can return for a given data read from the channel.
    //incomplete is returned if more data has to be read. Complete when the data is already read,
    //invalid if all data is read and the data does not confirm to the reader requirements.
    public enum msgreadstate { incomplete, complete, invalid }

    /**
     * The reader interface that has to be implemented that defines the data format
     * that is coming from the channel.
     * */
    public interface MessageReader
    {
        /**
         * The msg parameter to check for validity. THis should return
         * a msgreadstate. Return the length processed in len
         * */
        msgreadstate validMessage(ByteBuffer msg, ref int len);

        /**
         * This function should return the massaged object message from the 
         * ByteBuffer msg passed into this
         * function. Return the length processed in len
         * */
        Object formattedMsg(ByteBuffer msg, ref int len);

        /**
         * This function will format the given object message into a byte buffer to be
         * sent on the channel.
         * */
        ByteBuffer formatBuffer(Object msg);
    }
    
    /**
     * A simple reader helper class for ease of testing and reading on simple
     * channels.
     * */
    public class SimpleReader : MessageReader
    {
        //Default constructor.
        public SimpleReader()
        {
        }

        //All messages are validmessages.
        public msgreadstate validMessage(ByteBuffer msg, ref int len)
        {
            len = msg.Length;
            return msgreadstate.complete;
        }

        //the bytebuffer is not massaged.
        public Object formattedMsg(ByteBuffer msg, ref int len)
        {
            //there seems to be a problem of reusing the same object!!
            len = msg.Length;
            byte[] bytes = msg.getData();
            ByteBuffer duplicateme = new ByteBuffer(bytes.Length);
            duplicateme.Append(bytes, 0, bytes.Length);
            return duplicateme;
        }

        //assumes that the byte buffer is the msg passed in
        public ByteBuffer formatBuffer(Object msg)
        {
            return (ByteBuffer)msg;
        }
    }

    /**
     * This class is a handler class that handles formatting of messages that arrives on the channel. 
     * This handler should be added as the first handler that will run for upstream and the last handler
     * that will run for the downstream before the sink is called. 
     * */
    public class MessageFormatHandler : UpStreamHandler, DownStreamHandler
    {
        MessageReader _reader;              //The reader class that will be used to format the messages

        //default constructor and does not have any initialization
        public MessageFormatHandler()
        {
        }
        
        //Returns "MessageReader" as a handle for registration of this handler.
        public String handle { get { return "MessageReader"; } }

        /**
         * Registers a reader that this class has to use to format messages. If this reader is
         * not registered then this handler will not work correctly.
         * */
        public void registerReader(MessageReader reader)
        {
            _reader = reader;
        }

        /**
         * Called for upstream messages. This function calls the validmessage function in the reader
         * to check if all data has been read. If not, it stores the currently read buffer into the 
         * channel as an attachment. When data is received later on, the bytes are appended to this
         * attachment and checked again for availability of data. When the reader returns a complete
         * state, this function calls the formatmessage to return a formatted message for the next
         * set of handlers to handle.
         * */
        public HandlerState handleUpStream(HandlerContext ctx, ChannelEvent evt)
        {
            if (_reader == null)
                throw new Exception("MessageFormatHandler:handleUpStream:No Reader registered");

            HandlerState state = HandlerState.error;

            if (evt is UpstreamMessageEvent)
            {
                ChannelLogger.LOG.LogDebug("MessageFormatHandler:handleUpStream:Got one message to decode:" + evt.channel.channelid);
                UpstreamMessageEvent msgevt = evt as UpstreamMessageEvent;
                ByteBuffer fullmsg = null;
                ByteBuffer buffer = msgevt.getMessage() as ByteBuffer;
                if (evt.channel.attachment != null)
                {
                    fullmsg = evt.channel.attachment as ByteBuffer;
                    byte[] currbyte = buffer.getData();
                    fullmsg.Append(currbyte, 0, currbyte.Length);
                }
                else
                {
                    //first time for this message
                    fullmsg = buffer;
                }

                int len = 0;
                List<Object> formatted = new List<Object>();
                int msgs = 0;
                bool incomplete = false;
                while ((fullmsg.Length > 0) && (!incomplete))
                {
                    len = 0;
                    msgreadstate msgstate = _reader.validMessage(fullmsg, ref len);
                    switch (msgstate)
                    {
                        case msgreadstate.incomplete:
                            ChannelLogger.LOG.LogDebug("MessageFormatHandler:" + _reader.GetType().Name + ":replied incomplete");
                            evt.channel.attachment = buffer; //set attachment to be retrieved for the next receive
                            state = HandlerState.done; //don't continue to the next handler for this event
                            incomplete = true;
                            break;
                        case msgreadstate.complete:
                            len = 0;
                            evt.channel.attachment = null;
                            Object obj = _reader.formattedMsg(fullmsg, ref len);
                            formatted.Add(obj);
                            ChannelLogger.LOG.LogDebug("MessageFormatHandler:" + _reader.GetType().Name + ":replied completed:" + obj.GetType().Name + ":" + evt.channel.channelid);
                            msgs++;
                            fullmsg.truncate(len);
                            //ctx.streamUp(evt); //stream up to the next handler
                            state = HandlerState.gonext;
                            break;
                        case msgreadstate.invalid:
                        default:
                            //TODO: need to set the error somewhere and send a msg for it?
                            ChannelLogger.LOG.LogDebug("MessageFormatHandler:" + _reader.GetType().Name + ":replied invalid");
                            state = HandlerState.error;
                            break;
                    }
                }

                if (formatted.Count > 0) //atleast one message was received in full
                {
                    msgevt.transformMessage(formatted);
                    ctx.streamUp(evt); //stream these messages to be handled.
                    state = HandlerState.gonext; //continue next, the attachment will be picked up the next received.
                }
            }
            else if (evt is ChannelStateEvent)
            {
                //stream state events
                ctx.streamUp(evt);
                state = HandlerState.gonext;
            }

            //do not continue further since the message has no reader
            return state;
        }

        /**
         * This function is called in the downstream handlers. This function is called to translate
         * objects from the higher handlers to a bytebuffer to be written to the channel. This will be run
         * as the last handler in the downstream and hence after the object in the event will be only 
         * bytes to be written.
         * */
        public HandlerState handleDownStream(HandlerContext ctx, ChannelEvent evt)
        {
            if (evt is DownStreamMsgEvent)
            {
                DownStreamMsgEvent dmsg = evt as DownStreamMsgEvent;
                Object msg = dmsg.getMessage();
                ByteBuffer buff = _reader.formatBuffer(msg);
                buff.associated = msg; //associate, so this can be used later for other uses
                ChannelLogger.LOG.LogDebug("Got object: " + msg.GetType().Name + ": transformed : " + buff.Length + ":" + evt.channel.channelid);
                dmsg.transformMessage(buff);
            }

            evt.tracker.result = HandlerState.gonext;
            ctx.streamDown(evt);
            return HandlerState.gonext;
        }

        //dispose function when this class is disposed.
        public void Dispose()
        {
        }
    }
}
